#pragma once

#include <fstream>
#include <pugixml.hpp>
#include <boost/lexical_cast.hpp>
#include <unordered_map>
#include "common.h"
#include "dialoguetree.h"

class FileIO {
public:
	// Load all settings into namespace. Will create a file with default settings if not found.
	void loadSettings(const char* const path = "settings.xml");

	// Make sure to delete pointer to dialogue tree.
	dialogueTree loadDialogue(const char* const path);

	// Fails for very large maps. Rewrite to save in pieces. Does not save everything yet!
	void saveMap(TileMap* tileMap, const char* const path = "defaultMap.xml");
private:
	// Less safe but faster atoi()
	int naiveATOI(const char* p);
};

int FileIO::naiveATOI(const char* p) {
	int x = 0;
	bool neg = false;
	if (*p == '-') {
		neg = true;
		++p;
	}
	while (*p >= '0' && *p <= '9') {
		x = (x*10) + (*p - '0');
		++p;
	}
	if (neg) {
		x = -x;
	}
	return x;
}

void FileIO::loadSettings(const char* const path) {
	pugi::xml_document settingsFile;
	pugi::xml_parse_result result = settingsFile.load_file(path);
	if (result.status != pugi::xml_parse_status::status_ok) {
		std::cerr << "PugiXML has encountered a file loading error. Error: " << result.description() << ". FileIO::loadSettings(\"" << path << "\") will now replace the file with a default config.\n";
		settingsFile.reset();
		pugi::xml_node node = settingsFile.append_child("Settings");
		std::string temp;

		temp = boost::lexical_cast<std::string,short>(gameSettings::windowX);
		node.append_child("window_width").append_child(pugi::node_pcdata).set_value(temp.c_str());

		temp = boost::lexical_cast<std::string,short>(gameSettings::windowY);
		node.append_child("window_height").append_child(pugi::node_pcdata).set_value(temp.c_str());

		temp = boost::lexical_cast<std::string,short>(gameSettings::maxFPS);
		node.append_child("max_fps").append_child(pugi::node_pcdata).set_value(temp.c_str());

		temp = boost::lexical_cast<std::string,bool>(gameSettings::fullscreen);
		node.append_child("fullscreen").append_child(pugi::node_pcdata).set_value(temp.c_str());

		temp = boost::lexical_cast<std::string,bool>(gameSettings::showMouse);
		node.append_child("show_mouse").append_child(pugi::node_pcdata).set_value(temp.c_str());

		settingsFile.save_file(path);
	}
	else { // Unsafe. What if a node is missing?
		pugi::xml_node node = settingsFile.child("Settings");
		gameSettings::windowX = naiveATOI(node.child("window_width").child_value());
		gameSettings::windowY = naiveATOI(node.child("window_height").child_value());
		gameSettings::maxFPS = naiveATOI(node.child("max_fps").child_value());
		gameSettings::fullscreen = naiveATOI(node.child("fullscreen").child_value());
		gameSettings::showMouse = naiveATOI(node.child("show_mouse").child_value());
	}
}

dialogueTree FileIO::loadDialogue(const char* const path) {
	pugi::xml_document dialogueFile;
	pugi::xml_parse_result result = dialogueFile.load_file(path);
	if (result.status != pugi::xml_parse_status::status_ok) {
		std::cerr << "PugiXML has encountered a file loading error with FileIO::loadDialogue. Error: " << result.description() << ".\n";
		return dialogueTree();
	}
	else {
		dialogueTree dialogue;
		std::unordered_map<std::string,boost::graph_traits<dialogueTree>::vertex_descriptor> topicList; //Map of ID - Topic node pairs for edge adding
		pugi::xml_node tree = dialogueFile.child("Dialogue");
		// Add unlinked topic nodes
		for (pugi::xml_node topic = tree.child("Topic"); topic; topic = topic.next_sibling("Topic")) {
			dialogueTopic tempTopic;
			tempTopic.ID = topic.attribute("ID").value();
			tempTopic.topicText = topic.child_value("TopicText");
			for (pugi::xml_node response = topic.child("Responses").first_child(); response; response = response.next_sibling("Response")) {
				dialogueResponse tempResponse;
				tempResponse.priority = naiveATOI(response.child_value("Priority"));
				tempResponse.responseText = response.child_value("Text");
				tempResponse.farewell = naiveATOI(response.child_value("Farewell"));
				tempResponse.sayOnce = naiveATOI(response.child_value("SayOnce"));
				for (pugi::xml_node choice = response.child("Choices").first_child(); choice; response = choice.next_sibling("ID")) {
					tempResponse.choices.push_back(choice.child_value());
				}
				tempTopic.responses.push_back(tempResponse);
			}
			topicList[tempTopic.ID] = boost::add_vertex(tempTopic,dialogue);
		}
		// Iterate through each topic node to propery link it
		std::pair<	boost::graph_traits<dialogueTree>::vertex_iterator,
					boost::graph_traits<dialogueTree>::vertex_iterator
			> graph = boost::vertices(dialogue);
		std::for_each(graph.first, graph.second,
			[&] (boost::graph_traits<dialogueTree>::vertex_descriptor topicVertex) {
				std::for_each(dialogue[topicVertex].responses.begin(), dialogue[topicVertex].responses.end(),
					[&] (dialogueResponse response) {
						std::for_each(response.choices.begin(), response.choices.end(),
							[&] (std::string choiceID) {
								boost::add_edge(topicVertex,topicList[choiceID],dialogue);
							}
						);
					}
				);
			}
		);
		return dialogue;
	}
}

void FileIO::saveMap(TileMap* tileMap, const char* const path) { // Fails for very large maps.
		pugi::xml_document saveFile;
		pugi::xml_node node = saveFile.append_child("Map");
		for(unsigned int y = 0; y < tileMap->getHeight(); y++) {
			pugi::xml_node nodeY = node.append_child("Y");
			nodeY.append_attribute("pos") = y;
			for(unsigned int x = 0; x < tileMap->getWidth(); x++) {
				pugi::xml_node nodeX = nodeY.append_child("X");
				nodeX.append_attribute("pos") = x;
				nodeX.append_child("char").append_child(pugi::node_pcdata).set_value(&(tileMap->getTile(x, y)->c));
				std::string temp;

				temp = boost::lexical_cast<std::string,unsigned short>(tileMap->getTile(x, y)->backColour.r);
				nodeX.append_child("back_red").append_child(pugi::node_pcdata).set_value(temp.c_str());

				temp = boost::lexical_cast<std::string,unsigned short>(tileMap->getTile(x, y)->backColour.g);
				nodeX.append_child("back_green").append_child(pugi::node_pcdata).set_value(temp.c_str());

				temp = boost::lexical_cast<std::string,unsigned short>(tileMap->getTile(x, y)->backColour.b);
				nodeX.append_child("back_blue").append_child(pugi::node_pcdata).set_value(temp.c_str());

				temp = boost::lexical_cast<std::string,unsigned short>(tileMap->getTile(x, y)->foreColour.r);
				nodeX.append_child("fore_red").append_child(pugi::node_pcdata).set_value(temp.c_str());

				temp = boost::lexical_cast<std::string,unsigned short>(tileMap->getTile(x, y)->foreColour.g);
				nodeX.append_child("fore_green").append_child(pugi::node_pcdata).set_value(temp.c_str());

				temp = boost::lexical_cast<std::string,unsigned short>(tileMap->getTile(x, y)->foreColour.b);
				nodeX.append_child("fore_blue").append_child(pugi::node_pcdata).set_value(temp.c_str());

				temp = boost::lexical_cast<std::string,bool>(tileMap->getTile(x, y)->passable);
				nodeX.append_child("passable").append_child(pugi::node_pcdata).set_value(temp.c_str());

				temp = boost::lexical_cast<std::string,bool>(tileMap->getTile(x, y)->transparent);
				nodeX.append_child("transparent").append_child(pugi::node_pcdata).set_value(temp.c_str());

				pugi::xml_node actList = nodeX.append_child("ActorList");
				if (tileMap->getTile(x,y)->actorList.empty() != true)
					std::for_each(tileMap->getTile(x,y)->actorList.begin(),tileMap->getTile(x,y)->actorList.end(),
						[&] (Actor* actor) {
							pugi::xml_node act = actList.append_child("Actor");
							act.append_attribute("ID") = actor->ID;
							temp = boost::lexical_cast<std::string, unsigned char>(actor->getSym());
							act.append_child("Symbol").append_child(pugi::node_pcdata).set_value(temp.c_str());
						}
					);
			}
		}
		saveFile.save_file(path);
	}